import socket
import select
import threading
import time
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog,messagebox

import requests
import watchdog.observers
import watchdog.events
from log import AppLogger
from config import ConfigManager
from watchDir import watcher
import os
import sys
import queue
from datetime import datetime
import ipaddress


logger = AppLogger('log.log', max_bytes=1024*50, backup_count=10)
q = queue.Queue()
watcherFile = ''


def show_auto_close_messagebox(title,msg,width=300,height=100,second=3):
    root = tk.Tk()
    root.title(title)
    root.attributes('-topmost', True)
    # 防止用户调整窗口大小，相当于隐藏了最小化和最大化按钮的功能
    # root.resizable(0, 0)

    # 设置Label的wraplength属性来限制文本的宽度
    label = tk.Label(root, text=msg,  justify='center')
    label.pack(pady=20)  # 在窗口中垂直居中显示


    width = label.winfo_reqwidth()
    # 设置窗口的初始位置（居中）
    x = (root.winfo_screenwidth() // 2) - (width // 2)
    y = (root.winfo_screenheight() // 2) - (height // 2)
    root.geometry(f'{width}x{height}+{x}+{y}')
    
    # 创建一个OK按钮，点击后关闭窗口
    ok_button = tk.Button(root, text="OK", command=root.destroy)
    ok_button.pack()
    # threading.Thread(target=lambda: root.after(3000, root.destroy)).start()
    root.after(second*1000, root.destroy)
    root.mainloop() 
    
    
    
class FileModifiedHandler(watchdog.events.FileSystemEventHandler):
    def on_modified(self, event):
        src = os.path.normpath(event.src_path)
        wf = os.path.normpath(watcherFile)
        if event.is_directory  or src != wf:
            return
        lines = readLastLines(wf)
        anylyzeLog(lines)
        
class LogForm(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.master.title("Golden Sample")
        self.master.attributes('-topmost', True)
        # 使用 pack 布局
        self.pack(fill=tk.BOTH, expand=True)  # 使该 Frame 填满其父窗口
        self.master.resizable(False, False)
        # region 4 group
        self.leftFrame=tk.Frame(self)
        self.rightFrame=tk.Frame(self)
        self.leftFrame.pack(side="left", fill="both", expand=True, padx=5, pady=5)
        self.rightFrame.pack(side="right", fill="both", expand=True, padx=5, pady=5)
        # endregion
        # region groupBaseInfo
        self.groupBaseInfo = tk.LabelFrame(self.leftFrame, text="基础信息/Base Info")
        self.labelShiftTime = tk.Label(self.groupBaseInfo, text="换班时间/Shift Time:")
        self.entryShiftTime = tk.Entry(self.groupBaseInfo)
        self.btnShiftTime = tk.Button(self.groupBaseInfo, text="增加换时间/add shift time", command=self.addShiftTime)

        self.labelShiftTime.grid(row=0,column=0, padx=5, pady=5)
        self.entryShiftTime.grid(row=0,column=1, padx=5, pady=5)
        self.btnShiftTime.grid(row=0,column=2, padx=5, pady=5)
        
        self.listShiftTime = tk.Listbox(self.groupBaseInfo, height=5)
        self.btnRemoveShiftTime = tk.Button(self.groupBaseInfo, text="删除/remove", command=self.removeShiftTime)
        self.listShiftTime.grid(row=1,column=0,rowspan=3, columnspan=2, padx=5, pady=5,sticky='nesw')
        self.btnRemoveShiftTime.grid(row=1,column=2, padx=5, pady=5)
        
        self.labelIP = tk.Label(self.groupBaseInfo, text="IP地址/IP Address:")
        self.entryIP = tk.Entry(self.groupBaseInfo)   
        self.btnSetIp = tk.Button(self.groupBaseInfo, text="设置/setting ip", command=self.setIp)
        self.labelIP.grid(row=4,column=0, padx=5, pady=5)
        self.entryIP.grid(row=4,column=1, padx=5, pady=5)
        self.btnSetIp.grid(row=4,column=2, padx=5, pady=5)
        # endregion groupBaseInfo
    
        # region groupLog
        self.groupLog = tk.LabelFrame(self.rightFrame, text="分析日志/Log")
        self.entryLogPath = tk.Entry(self.groupLog)
        self.btnSelectLogPath = tk.Button(self.groupLog, text="选择日志路径/Select Log Path", command=self.chooseLogPath)
        self.entryLogPath.grid(row=0,column=0, columnspan=2, padx=5, pady=5,sticky='ew')
        self.btnSelectLogPath.grid(row=0,column=2, padx=5, pady=5,sticky='ew')
        
        self.btnAnalyze = tk.Button(self.groupLog, text="分析/Analyze", command=self.analyze)
        self.btnAnalyze.grid(row=2,column=0, padx=5, pady=5,columnspan=1)
        self.btnSendCompare = tk.Button
        
        self.btnSendCompare = tk.Button(self.groupLog, text="发送对比请求/send compare", command=self.compareSample)
        self.btnSendCompare.grid(row=2,column=1, padx=5, pady=5,columnspan=1)
        
        # self.btnStop = tk.Button(self.groupLog, text="退出/testbtn", command=self.testbtn)
        # self.btnStop.grid(row=2,column=2, padx=5, pady=5,columnspan=1)

        self.textBox = tk.Text(self.groupLog, height=15)
        self.textBox.grid(row=3,column=0,rowspan=3, columnspan=3, padx=5, pady=5,sticky='nesw')        
        
        self.groupLog.grid_columnconfigure(0, weight=1)  # 第一列可扩展
        self.groupLog.grid_columnconfigure(1, weight=1)  # 第二列可扩展
        # endregion groupLog
        
        # region groupSample
        self.groupSample = tk.LabelFrame(self.leftFrame, text="样本获取/Sample")
        # self.labelTolerance = tk.Label(self.groupSample, text="公差值/Tolerance:")
        self.entryTolerance = tk.Entry(self.groupSample)
        self.btnTolerance = tk.Button(self.groupSample, text="增加样本/add sample", command=self.addTolerance)
        self.btnToleranceEdit = tk.Button(self.groupSample, text="修改公差/edit Tolerance", command=self.editTolerance)
        # self.labelTolerance.grid(row=0,column=0, padx=5, pady=5)
        self.btnTolerance.grid(row=0,column=0, padx=5, pady=5)
        self.entryTolerance.grid(row=0,column=1, padx=5, pady=5)
        self.btnToleranceEdit.grid(row=0,column=2, padx=5, pady=5)
        
        self.frame1 = tk.Frame(self.groupSample)
        self.frame1.grid(row=1,column=0,rowspan=1, columnspan=3, padx=5, pady=5,sticky='nesw')
        
        self.entrySampleName = tk.Entry(self.frame1)
        self.btnSearchSample = tk.Button(self.frame1, text="搜索/Search", command=self.searchSample)
        self.btnRemoveSample = tk.Button(self.frame1, text="删除选中/Remove", command=self.removeSample)
        
        self.entrySampleName.pack(side="left", padx=5, pady=5,fill='x',expand=True)
        self.btnSearchSample.pack(side="left", padx=5, pady=5)
        self.btnRemoveSample.pack(side="right", padx=5, pady=5)

        self.listSample = tk.Listbox(self.groupSample,  height=10)
        # 绑定双击事件
        self.listSample.bind("<Double-Button-1>", self.doubleClickSample)
        self.listSample.grid(row=2,column=0,rowspan=3, columnspan=4, padx=5, pady=5,sticky='nesw')
        
        self.lablePageInfo = tk.Label(self.groupSample, text="")
        self.comboBoxPage = ttk.Combobox(self.groupSample,  values=(), state="readonly") 
        self.comboBoxPage.bind("<<ComboboxSelected>>", self.changePage)
        self.lablePageInfo.grid(row=5,column=0, padx=5, pady=5,columnspan=2)
        self.comboBoxPage.grid(row=5,column=2, padx=5, pady=5)

        self.labelSelectedSample = tk.Label(self.groupSample, text="")
        self.btnSelectSample = tk.Button(self.groupSample, text="使用选择的样本/Select Sample", command=self.chooseSample)
        self.labelSelectedSample.grid(row=6,column=0, padx=5, pady=5,columnspan=2)
        self.btnSelectSample.grid(row=6,column=2, padx=5, pady=5)
                
        self.groupDetail = tk.LabelFrame(self.rightFrame, text="详细信息/Detail")
        self.listBoxtDetail = tk.Listbox(self.groupDetail)
        self.listBoxtDetail.grid(row=0, column=0, padx=5, pady=5, sticky='nsew')
          # 配置列和行以可扩展
        self.groupDetail.grid_columnconfigure(0, weight=1)  # 第一列可扩展
        self.groupDetail.grid_rowconfigure(0, weight=1)    # 第一行可扩展
        # endregion groupSample
        
        # region 4 group pack
        self.groupBaseInfo.pack(side="top", padx=5, pady=5)
        self.groupLog.pack(side="top", padx=5, pady=5,fill='both')
        self.groupSample.pack(side="bottom", padx=5, pady=5,expand=True,fill='x')
        self.groupDetail.pack(side="bottom", padx=5, pady=5,expand=True,fill='both')
        # endregion
        
        self.analyzing=False
        self.setBtns()
        self.init()        
        # 绑定窗口关闭事件
        self.master.protocol("WM_DELETE_WINDOW", self.on_close)

    def on_close(self):
        # 如果确认对话框返回 True，则关闭窗口；否则不关闭
        # if tk.messagebox.askyesno("提示:", "您确定要关闭窗口吗？"):
        self.udpThreadRunning=False
        self.stopWatcher()
        self.master.destroy()
    
    def appendText(self,text):
        # 判断 self.textBox 有多少数据
        if len(self.textBox.get("1.0", "end-1c"))>5000:
            self.clearText()

        self.textBox.insert(tk.END, text)
        self.textBox.insert(tk.END, "\n")
        self.textBox.see(tk.END) 
        self.textBox.yview_moveto(1.0)  # 确保滚动条移动到最底部
        
    def clearText(self):
        self.textBox.delete(1.0, tk.END)            
    def init(self):
        # 初始化配置文件 logconfig.ini
        self.config = ConfigManager('logconfig.ini')
        self.q = q
        # region 基础信息
        value = self.config.get_option('SHIFTTIME', 'times')
        valuses = value.split(';')
        for value in valuses:
            value and  self.listShiftTime.insert(tk.END, value)
        
        value = self.config.get_option('IP','ip')
        self.entryIP.delete(0, tk.END)  # 删除所有现有文本
        self.entryIP.insert(tk.END, value)
        
        value = self.config.get_option('SAMPLE','name')
        self.labelSelectedSample.configure(text=value)
        # endregion
        
        # region 分析日志
        value = self.config.get_option('LOG', 'logfile')
        self.entryLogPath.delete(0, tk.END)  # 删除所有现有文本
        self.entryLogPath.insert(tk.END, value)
        # endregion
        self.initUdp()
        self.after(500, self.updateMsg)
        self.analyze()
    # region udp线程
    def initUdp(self):
        # 开启一个新线程
        self.udpThreadRunning=True
        self.udpThread = threading.Thread(target=self.udpServer)
        self.udpThread.start()
    def udpServer(self):
        # 建立一个服务，用于反复接收 UDP 数据
        server_address = ('', 9090)  # 监听所有接口上的 9090 端口
        print(f"Starting UDP server on {server_address[0]} port {server_address[1]}")

        try:
            # 创建 UDP socket
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.sock.bind(server_address)
            print("UDP socket bound to", server_address)

            while self.udpThreadRunning:
                # 使用 select 获取准备就绪的 socket
                r, w, x = select.select([self.sock], [], [], 0.5)  # 0.5 秒超时

                if self.sock not in r:
                    continue
                # 处理收到的数据
                data, address = self.sock.recvfrom(4096)
                # 判断data 是str  还是byte
                if isinstance(data, bytes):   
                    data = data.decode('utf-8')
                self.q.put(data)
                print(f"Received {len(data)} bytes from {address}: {data}")
                if data.startswith('sample_ok'):
                    self.btnTolerance.config(state='normal',text="增加样本/add sample")
                # elif data.startswith('compare_ok'):
                #     # compare_ok
                #     self.appendText("compare_ok")
                # elif data.startswith('compare_fail'):
                #     # compare_fail
                #     self.appendText("compare_fail")
                # elif data.startswith('compare_error'):
                #     # compare_error
                #     self.appendText("compare_error")
        except Exception as ex:
            print(ex)
        finally:
            self.sock.close()

    # endregion udp线程
       
        
    def watcherThread(self,path_to_watch):
        event_handler = FileModifiedHandler()
        self.observer = watchdog.observers.Observer()
        self.analyzing = True
        global last_log_time, watcherFile
        watcherFile = path_to_watch
        self.observer.schedule(event_handler, path=os.path.dirname(path_to_watch), recursive=False)  # 设置为 False，仅监控指定路径
        self.observer.start()
        print("self.observer.start()" + path_to_watch)
        # 获取 listShiftTime 中的全部换班时间
        shiftTimes = self.listShiftTime.get(0, tk.END)
        try:
            while self.analyzing:
                time.sleep(0.5)
                # 获取当前时间 '%H:%M:%S'
                now = datetime.now().strftime('%H:%M:%S')
                # print("self.analyzing"+now)
                if now in shiftTimes:
                    # 触发换班
                    q.put(f'alert:换班时间:{now}')
        except Exception as ex:
            pass
  
        print("self.observer.stop()")
        self.stopWatcher()
        
    def startWatcher(self):
        # 获取最后一个 程序名 最后一行的时间，
        getLastTimePrograme(self.entryLogPath.get())
        
        if last_programe_name=='' :
            messagebox.showerror("提示", "请先确保配置文件里有程序名")
            return
        if last_log_time =='':
            messagebox.showerror("提示", "请先确保配置文件里有处理时间")
            return
        self.analyzing = True
        self.watcher = threading.Thread(target=self.watcherThread, args=(self.entryLogPath.get(),))
        self.watcher.start()
        self.appendText("\n开始监控文件："+self.entryLogPath.get())
        
    def stopWatcher(self):
        if self.analyzing:
            self.analyzing=False
            self.appendText("结束监控文件")
            self.observer.stop()
            self.observer.join()
        self.setBtns()
        
    def updateMsg(self):
        while not self.q.empty():
            data = self.q.get()
            if isinstance(data, bytes):   
                data = data.decode('utf-8')
            # data = str(data)
            print("after:"+data)
            if data.startswith('alert:'):
                # 暂停监控文件
                self.stopWatcher()
                # 发出警告
                self.appendText(data[6:])
                
                sample_name = self.labelSelectedSample.cget("text")
                if not messagebox.askokcancel("",f"请放入{sample_name}样品进行测试"):
                    logger.info(datetime.now().strftime('%Y-%m-%d %H:%M:%S')+"用户选择取消；"+data)
                    self.analyze()
                else:
                    threading.Thread(target = self.compareSample).start()
            elif data.startswith('compare_fail:'):
                self.appendText("对比失败"+data)
                logger.info(datetime.now().strftime('%Y-%m-%d %H:%M:%S')+" 对比失败："+data)
                self.analyze()
                if messagebox.askyesno("提示：","对比失败，继续对比？"):
                    threading.Thread(target = self.compareSample).start()
                else:
                    logger.info(datetime.now().strftime('%Y-%m-%d %H:%M:%S')+"用户选择停止")
            elif data.startswith('compare_error:'):
                self.appendText("对比出错："+data)
                logger.info(datetime.now().strftime('%Y-%m-%d %H:%M:%S')+" 对比出错："+data)
                if messagebox.askyesno("提示：","对比出错，继续对比？"):
                    threading.Thread(target = self.compareSample).start()
                else:
                    # log  user stopped
                    logger.info(datetime.now().strftime('%Y-%m-%d %H:%M:%S')+"用户选择停止")
            elif data.startswith('compare_ok:'):
                self.appendText("对比成功")
                # threading.Thread(target = messagebox.showinfo,args=("提示：","对比成功")).start()
                show_auto_close_messagebox("提示：","对比成功")
                time.sleep(3)
                threading.Thread(target=self.analyze).start()
                
            elif data.startswith('sample_ok:'):
                self.answerTorlerance()
                self.appendText(data[10:])
            else:
                self.appendText(data)
                
        self.after(500, self.updateMsg)
        
    #region groupBaseInfo
    def addShiftTime(self):
        # 获取 self.entryShiftTime 的值
        value = self.entryShiftTime.get()
        try:
            # 将值转换为YYYY-MM-DD HH:MM格式
            datetime.strptime(value, '%H:%M:%S')
        except Exception as ex:
            messagebox.showerror('错误', '时间格式错误:13:10:20')
            return
        try:
            # 增加到 self.listShiftTime 中
            self.listShiftTime.insert(tk.END, value)
            # set_option(self, section, option, value,saveImmediately =False):
            self.config.set_option('SHIFTTIME', 'times', value,True)
            self.entryShiftTime.delete(0, tk.END)
        except Exception as ex:
            messagebox.showerror('保存出错', str(ex))
        
    
    def removeShiftTime(self):
        # 删除    listShiftTime 里选中的 item 并把余下的 item 以;拼接 写入config里
        selected_items = self.listShiftTime.curselection()
        if selected_items:
            for index in reversed(selected_items):
                self.listShiftTime.delete(index)
            # 获取 self.listShiftTime 的值
            values = self.listShiftTime.get(0, tk.END)
            # 将值转换为YYYY-MM-DD HH:MM:SS格式
            values = [value for value in values if value]
        self.config.set_option('SHIFTTIME', 'times', ";".join(values) ,True)
        

    def setIp(self):
        # 获取 self.entryIP 的值
        value = self.entryIP.get().strip()
        # 判断 value 是否为合法 ipv4 地址
        try:
            ipaddress.ip_address(value)
            self.config.set_option('IP', 'ip', value,True)
        except Exception as ex:
            messagebox.showerror('保存ip地址错误', 'IP地址参考格式:192.168.1.1')
    #endregion groupBaseInfo

    #region groupSample
    def addTolerance(self):
        value = self.entryTolerance.get().strip()
        ip = self.config.get_option('IP', 'ip')
        
        # 判断 value 是否为合法 整数
        try:
            int(value)
        except Exception as ex:
            messagebox.showerror('公差错误', '公差必须是整数')
            return
        
        try:
            # 使用标准库发出一个get请求
            r = requests.get(f'http://{ip}:8080/new?tolerance={value}')
            # 获取响应内容
            data = r.json()
            print(data)
            self.btnTolerance.configure(state='disabled', text="等待中...")
        except Exception as ex:
            messagebox.showerror('增加失败：', str(ex))

    def editTolerance(self):
        value = self.entryTolerance.get().strip()
        ip = self.config.get_option('IP', 'ip')
        
        # 判断 value 是否为合法 整数
        try:
            int(value)
        except Exception as ex:
            messagebox.showerror('公差错误', '公差必须是整数')
            return

        # 获取 self.listSample 选中的 item
        selected_items = self.listSample.curselection()
        if len(selected_items)<=0:
            messagebox.showerror('提示', '只能点选一个样本')
            return
        
        # 获取 self.listSample 选中的 item
        name = self.listSample.get(selected_items[0])
        
        try:
            # 使用标准库发出一个get请求
            r = requests.get(f'http://{ip}:8080/edit?tolerance={value}&program={name}')
            if r.status_code != 200 :
                self.appendText(f"修改数据失败:\n{r.text}")
                show_auto_close_messagebox("提示",f"修改数据失败:\n{r.text}", 3)
            else:
                data = r.json()
                show_auto_close_messagebox('提示：',data['msg'])
        except Exception as ex:
            messagebox.showerror('增加公差失败：', str(ex))


    def answerTorlerance(self):
        self.btnTolerance.configure(state='normal', text="增加公差/Add Tolerance")
        # 给出一个提示
        messagebox.showinfo('提示', '增加公差成功')
    
    def compareSample(self):
        sample_name = self.labelSelectedSample.cget("text")
        if sample_name == "" :
                messagebox.showerror('提示', '请选择一个样本')
                return
        # 获取 ip 
        ip = self.config.get_option('IP', 'ip')
        self.clearText()
        self.appendText(f"发送对比请求：{ip},{sample_name}")
        try:
            r = requests.get(f'http://{ip}:8080/compare?program={sample_name}')
            if r.status_code != 200 :
                messagebox.showerror('系统错误', '服务器返回错误')
                return
            else:
                self.appendText("正在对比中...")
                # # 继续分析日志
                # self.startWatcher()

        except Exception as ex:
            messagebox.showerror('发送网络请求失败', str(ex))

    def searchSample(self,page=1,pagesize=20):
        value = self.entrySampleName.get().strip()
        # 获取 ip 
        ip = self.config.get_option('IP', 'ip')
        try:
            r = requests.get(f'http://{ip}:8080/programes?program={value}&page={page}&pagesize={pagesize}')
            # self.textBox.delete('1.0', tk.END)
            # self.textBox.insert(tk.END, r.text)
            if r.status_code != 200 :
                messagebox.showerror('查找数据失败', '服务器返回错误')
                return
            # 获取响应内容
            data = r.json()
            if  data['code']!=200:
                print(data)
                messagebox.showerror('查找数据失败', data['msg'])
                return
            
            # 清空列表 listSample， 写入列表；清空page，增加page 写入页面信息提示
            self.listSample.delete(0, tk.END)
            self.comboBoxPage.delete(0, tk.END)
            self.lablePageInfo.config(text=f"共{data['data']['total']}条，共{data['data']['pages']}页，每页{data['data']['pagesize']}条，当前页{data['data']['page']}页")
            self.comboBoxPage['values'] = [str(i)+"页" for i in range(1,data['data']['pages']+1)]
            # 遍历data，将每个字典的name属性写入列表；
            # 将每个字典的id属性写入列表；将每个字典的name属性写入列表；
            for f in data['data']['files']:
                self.listSample.insert(tk.END,f)
            
        except Exception as ex:
            messagebox.showerror('查找失败', str(ex))
    def changePage(self,e):
        # 获取当前页面
        page = self.comboBoxPage.get()
        print("changePage："+page)
        page = int(page[:-1])        
        self.searchSample(page=page,pagesize=20)
    def removeSample(self):
        ip = self.config.get_option('IP', 'ip')
        
        # 获取 self.listSample 选中的 item
        selected_items = self.listSample.curselection()
        if len(selected_items)!=1:
            messagebox.showinfo("提示","请选中一行")
            return
        # 获取 self.listSample 选中的 item
        name = self.listSample.get(selected_items[0])
        print(name)
        
        try:
            # 使用标准库发出一个get请求
            r = requests.get(f'http://{ip}:8080/delete?program={name}')
            data = r.json()
            messagebox.showinfo('提示：', data['msg'])
            if data['code']==200:
                # 删除列表中的item
                self.listSample.delete(selected_items[0])
        except Exception as ex:
            messagebox.showerror('删除数据失败', str(ex))
            return
        
        for index in reversed(selected_items):
            self.listShiftTime.delete(index)
            return
    
    def doubleClickSample(self,e):
        # 获取 self.entryIP 的值
        ip = self.entryIP.get().strip()
        # 获取 self.listSample 选中的 item
        selected_items = self.listSample.curselection()
        # 获取 self.listSample 选中的 item
        name = self.listSample.get(selected_items[0])
        print(name)
        try:
            r = requests.get(f'http://{ip}:8080/programe?program={name}')
            if r.status_code != 200 :
                messagebox.showerror('获取失败', '服务器返回错误')
                return
            # 获取响应内容
            data = r.json()
            if  data['code']!=200:
                print(data)
                messagebox.showerror('获取详情', data['msg'])
                return
            self.listBoxtDetail.delete(0, tk.END)
            for k,v in data['data'].items():
                self.listBoxtDetail.insert(tk.END,f"{k}:{v}")
        except Exception as ex:
            messagebox.showerror('获取详情失败', str(ex))
            
    #endregion groupSample
    
    #region log
    def chooseLogPath(self):
        # 弹出一个文件选择对话框，只选择csv文件
        r = filedialog.askopenfilename(filetypes=[("csv文件", "*.csv")])
        if not r:
            return 
        self.config.set_option('LOG','logfile',r,True)
        # 设置 self.entryLogPath 的值
        self.entryLogPath.delete(0, tk.END)
        self.entryLogPath.insert(0, r)
    
    def chooseSample(self):
        # 获取 self.listSample 选中的 item
        selected_items = self.listSample.curselection()
        if len(selected_items)!=1:
            messagebox.showinfo("提示","请选中一行")
            return
        # 获取 self.listSample 选中的 item
        name = self.listSample.get(selected_items[0])
        self.labelSelectedSample.config(text=name)
        self.config.set_option('SAMPLE','name',name,True)

         
    def setBtns(self):
        '''根据状态设置 三个按钮状态'''
        if self.analyzing:
            self.btnSelectLogPath.config(state='disabled')
            self.btnSelectSample.config(state='disabled')
            self.btnAnalyze.config(text='点此停止.../stoped')
            self.entryIP.config(state='disabled')
            self.btnSetIp.config(state='disabled')
        else:
            self.btnSelectLogPath.config(state='normal')
            self.btnSelectSample.config(state='normal')
            self.btnAnalyze.config(text='点此分析.../analyze...')
            self.entryIP.config(state='normal')
            self.btnSetIp.config(state='normal')
            
    def analyze(self):
        # 分析按钮事件
        # 当前在分析中  
        if self.analyzing:
            # 结束日志监控
            self.stopWatcher()
            self.setBtns()
            return
        # 获取 self.entryLogPath 的值
        log_path = self.entryLogPath.get().strip()
        if log_path == "" or not os.path.exists(log_path):
            messagebox.showerror('提示', '请选择日志文件')
            return
        # 获取 self.labelSelectedSample 的值
        sample_name = self.labelSelectedSample.cget("text")
        if sample_name == "" :
            messagebox.showerror('提示', '请选择一个样本')
            return
        
        # 开启日志监控
        self.startWatcher()
        self.setBtns()

    def testbtn(self):
        # t =threading.Thread(target=messagebox.showerror,args=("aa","bb"))
        # t.start()

        pass

    #endregion log

# 以下是监控分析日志用的到全局变量
last_log_time =  "" # 日志每行有日期时间串：2024/03/30 14:12:49
last_programe_name = ""
last_line = ''  #程序上次分析时 日志的最后一行

def getLastTimePrograme(file_path):
    global last_log_time,last_programe_name,last_line
    last_log_time,last_programe_name = '' , ''
    last_line = ""
    
    lines = []
    for i in range(1,5):
        try:
            with open(file_path, 'r',encoding='utf-8') as f:
                lines = f.readlines()
        except Exception as e:
            time.sleep(0.1)
            continue
    # 倒着遍历 lines
    for line in reversed(lines):
        if not line.startswith('20'):
            continue
        fields = line.split(',')
        if len(fields)<5:
            continue
        if last_line =='':
            last_line = line
        # 获取行中的日期时间串
        if not last_log_time:
            last_log_time = fields[0].strip()
            
        # 获取行中的程序名
        if not last_programe_name:
            last_programe_name = fields[1].strip()

        if last_log_time and last_programe_name:
            return last_log_time , last_programe_name
    
def readLastLines(file_path, num_bytes=1000):
    '''读取文件最后 num_bytes 个字节'''
    last_bytes=0
    for i in range(1,5):
        try:
            with open(file_path, 'rb') as f:
                f.seek(0, 2)  # 移动到文件末尾
                file_size = f.tell()  # 获取文件大小
                f.seek(max(0, file_size - num_bytes))  # 确保不会超出文件开头
                last_bytes = f.read()
                break
        #捕获文件不可读的异常   
        except  Exception as e:
            time.sleep(0.1)
            continue
    else:
        # todo log: read logfile failed
        return []
    s=''
    for i in range(0,3):
        try:
            s = last_bytes[i:].decode('utf-8')
            break
        except Exception as e:
            pass
    else:
        return []
    return s.splitlines()[1:]

def anylyzeLog(lines):
    global last_log_time,last_programe_name,last_line
    # 当OperatorLog文件中出现“ 程序名”项出现变化
    # “详细”项中出现“保存基板程序”
    last_time = None
    for line in lines:
        if not line.startswith('20'):
            continue
        fields = line.strip().split(',')
        if len(fields)<5:
            q.put("日志格式错："+line.strip())
            continue
        
        if fields[0] < last_log_time :
            continue
        if not last_time:
            last_time = fields[0]
        if fields[1] and fields[1] != last_programe_name and line.strip() != last_line.strip():
            last_programe_name = fields[1]
            q.put('alert:'+line)
            # q.put('alert:'+"程序名:"+last_programe_name)
            # 触发
            last_log_time = last_time
            last_line=line
            break
        if fields[-1] == '保存基板程序'and line.strip() != last_line.strip():
            # 触发
            q.put('alert:'+line)
            last_log_time = last_time
            last_line=line
            return
    q.put(datetime.now().strftime('新日志 %H:%M:%S,')+"程序名:"+fields[1]+"详细:"+fields[-1]+"")

            
            
if __name__ == '__main__':
    app = LogForm(master= tk.Tk())
    
    app.mainloop()